using Lombiq.HelpfulLibraries.Libraries.DateTime;
using Microsoft.AspNetCore.Http;
using System;
using System.Threading.Tasks;

namespace OrchardCore.Modules;

public static class LocalClockExtensions
{
    /// <summary>
    /// 通过在 HTTP 上下文中临时设置，将给定的 UTC 日期转换为给定的时区。
    /// </summary>
    /// <param name="dateTimeUtc">UTC 日期。</param>
    /// <param name="timeZoneId">IANA 时区 ID。</param>
    /// <param name="httpContext">用于在 HTTP 上下文中临时设置的 HTTP 上下文。</param>
    /// <returns>本地日期。</returns>
    public static async Task<DateTime> ConvertToLocalAsync(
        this ILocalClock localClock,
        DateTime dateTimeUtc,
        string timeZoneId,
        HttpContext httpContext) =>
        (await ExecuteInDifferentTimeZoneAsync(
            httpContext,
            timeZoneId,
            () => localClock.ConvertToLocalAsync(ForceUtc(dateTimeUtc)))).DateTime;

    /// <summary>
    /// 通过在 HTTP 上下文中临时设置，使用给定的时区将给定的本地日期转换为 UTC。
    /// </summary>
    /// <param name="dateTimeLocal">本地日期。</param>
    /// <param name="timeZoneId">IANA 时区 ID。</param>
    /// <param name="httpContext">用于在 HTTP 上下文中临时设置的 HTTP 上下文。</param>
    /// <returns>UTC 日期。</returns>
    public static Task<DateTime> ConvertToUtcAsync(
        this ILocalClock localClock,
        DateTime dateTimeLocal,
        string timeZoneId,
        HttpContext httpContext) =>
        ExecuteInDifferentTimeZoneAsync(
            httpContext,
            timeZoneId,
            () => localClock.ConvertToUtcAsync(dateTimeLocal));

    /// <summary>
    /// 将 UTC DateTime 转换为本地时间并格式化为长时间格式。<paramref name="dateTimeUtc"/> 必须是 UTC。
    /// 如果 <see cref="DateTime.Kind"/> 不是 <see cref="DateTimeKind.Utc"/>，则将在不进行任何转换的情况下强制转换。
    /// 如果需要转换，请先使用 <see cref="ConvertToUtcAsync"/>。
    /// </summary>
    public static async Task<string> LocalizeAndFormatAsync(
        this ILocalClock localClock,
        DateTime? dateTimeUtc)
    {
        if (dateTimeUtc == null) return null;

        return ((DateTime?)(await localClock.ConvertToLocalAsync(dateTime: dateTimeUtc.Value)).DateTime).ToString();
    }

    private static async Task<T> ExecuteInDifferentTimeZoneAsync<T>(HttpContext httpContext, string timeZoneId, Func<Task<T>> asyncAction)
    {
        var previousTimeZoneId = httpContext.GetTimeZoneId();
        httpContext.SetTimeZoneId(timeZoneId);

        var result = await asyncAction();

        if (previousTimeZoneId == null) httpContext.Items.Remove(HttpContextKeys.TimeZoneIdKey);
        else httpContext.SetTimeZoneId(previousTimeZoneId);

        return result;
    }

    private static DateTimeOffset ForceUtc(DateTime dateTimeUtc) =>
        new(dateTimeUtc.Kind != DateTimeKind.Utc
            ? new DateTime(dateTimeUtc.Ticks, DateTimeKind.Utc)
            : dateTimeUtc);
}
